home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 2410 / 2410.xpi / chrome / content / foxmarks-network.js < prev    next >
Text File  |  2010-01-28  |  15KB  |  376 lines

  1. /* 
  2.  Copyright 2007-2009 Xmarks Inc.
  3.  
  4.  foxmarks-network.js: implements network interface to Syncd2.
  5.  
  6.  */
  7.  
  8. const NS_ERROR_REDIRECT_LOOP = 2152398879;
  9. const NS_ERROR_CWD_ERROR = 0x804b0016;
  10.  
  11. /*
  12.  
  13.  The Request class handles communication with a server. It is essentially
  14.  an HTTP Javascript remote object broker, delivering objects from the client
  15.  to the server and retrieving Javascript objects from the server in return.
  16.  Request has built-in support for the Foxmarks authentication protocol,
  17.  initiated by a 302 Redirect response.
  18.  
  19.  Arguments:
  20.      method: a string indicating which HTTP method to use.
  21.      url: either a string or an object containing any of
  22.         protocol, host, and path. If the caller provides a string, that
  23.         literal string is used as the target url. If an object is
  24.         supplied, Request uses whatever components are provided to construct
  25.         a final url, employing defaults for missing components.
  26.      obj: the object to be transmitted to the server.
  27.     opt: an object that contains optional information as described below
  28.  
  29.  Return value:
  30.     Three different types of errors can occur in processing a request.
  31.     1) Network error: DNS failure, connection reset, etc.
  32.     2) Transport failure: HTTP 404, etc.
  33.     3) App failure: syncd rejects a request because of revision mismatch.
  34.  
  35.  If an error occurs, we return an integer status code in response.status.
  36.  
  37.  
  38.  NEW opt parameter is a object/dict with optional values
  39.     isAuthRequest - whether this is an authenticate request
  40.     headers - any http headers that should be passed in
  41.     ignoreBody - don't do any conversion of the body
  42.     ignoreAuthTokens - don't update or check for authtokens
  43.     noAuthDialog - authenticate but only if it can be done w/o UI
  44.     noJSON - the data being returned isn't json so don't parse it
  45.  */
  46.  
  47.  
  48. function Request(method, url, bodyobj, opt) {
  49.     opt = opt || {};
  50.     this._channel = null;
  51.     this._streamLoader = null;
  52.     this._callback = null;
  53.     this._triedAuth = false;
  54.     this._headers = opt.headers;
  55.     this._ignoreBody = opt.ignoreBody;
  56.     this._ignoreAuthTokens = opt.ignoreAuthTokens;
  57.     this._noJSON = opt.noJSON;
  58.     this._noAuthDialog = opt.noAuthDialog;
  59.     try {
  60.         this.JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
  61.     } catch(e){
  62.         this.JSON = null;
  63.     }
  64.     
  65.     // Argument url is either a string or an object
  66.     // consisting of protocol, host, and path.
  67.     // If it's a string, just take the string that's been
  68.     // given. If it's an object, assemble the components
  69.     // (applying defaults as appropriate) into a url.
  70.  
  71.     if (url instanceof Object) {
  72.  
  73.         // Set up defaults.
  74.         if (!url.protocol) {
  75.             if (Xmarks.gSettings.securityLevel == 1 ||
  76.                 (Xmarks.gSettings.securityLevel == 0 && opt.isAuthRequest)) {
  77.                     url.protocol = "https";
  78.             } else {
  79.                 url.protocol = "http";
  80.             }
  81.         }
  82.         if (!url.host) url.host = Xmarks.gSettings.host;
  83.         if (!url.path) url.path = "/";
  84.         if (url.path[0] != "/") url.path = "/" + url.path;
  85.         this._url = url.protocol + "://" + url.host + url.path;
  86.     } else {
  87.         this._url = url;
  88.     }
  89.     this._bodyobj = bodyobj;
  90.     this._method = method;
  91.     this._isAuthRequest = opt.isAuthRequest;
  92. }
  93.  
  94. Request.prototype = {
  95.  
  96.     /*
  97.  
  98.     Process a request. Given the protocol, host, and path, construct a
  99.     url and execute the specified method against that url, inserting
  100.     the given message body.
  101.  
  102.     When request completes, it calls the provided callback function,
  103.     passing a single object (the "response object"). 
  104.  
  105.     */
  106.  
  107.     Start: function(callback) {
  108.         Xmarks.LogWrite(">>> " + this._method + " " + this._StripPassword(this._url));
  109.         if (this._bodyobj && !this._isAuthRequest) {
  110.             Xmarks.LogWrite(">>> Body is: " + 
  111.                 (!Xmarks.gSettings.getDebugOption("no-verbose") ?
  112.                 this._bodyobj.toJSONString() : "(disabled)"));
  113.         }
  114.         this._callback = callback;
  115.  
  116.         // Create a channel.
  117.         var ios = Cc["@mozilla.org/network/io-service;1"].
  118.             getService(Ci.nsIIOService);
  119.         try {
  120.             this._channel = ios.newChannelFromURI(ios.newURI(this._url, 
  121.                     "UTF-8", null));
  122.         } catch (e) {
  123.             Xmarks.LogWrite("Couldn't create channel from " + this._url + ", error:" + e.toSource());
  124.             callback(1008);
  125.             return;
  126.         }
  127.  
  128.         this._channel.QueryInterface(Ci.nsIUploadChannel);
  129.         this._channel.loadFlags |= 
  130.             Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
  131.  
  132.         // If we have a body to transmit, set it up as an upload stream.
  133.         if (this._bodyobj) {
  134.             var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  135.                 createInstance(Ci.nsIScriptableUnicodeConverter);
  136.             converter.charset = "UTF-8";
  137.             var stream = converter.convertToInputStream(
  138.                 this._bodyobj.toJSONString());
  139.             this._channel.setUploadStream(stream, "application/json", -1);
  140.         }
  141.  
  142.         // Special setup only for HTTP channels.
  143.         if (this._channel instanceof Ci.nsIHttpChannel) {
  144.  
  145.             // Set the channel's method if necessary.
  146.             if (this._method) {
  147.                 this._channel.requestMethod = this._method;
  148.             }
  149.  
  150.             // Disable redirection.
  151.             this._channel.redirectionLimit = 0;
  152.             this._channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
  153.  
  154.             // Set the user agent.
  155.             this._channel.setRequestHeader("User-Agent", 
  156.                 this._channel.getRequestHeader("User-Agent") + 
  157.                     " Xmarks-Fx/" + Xmarks.FoxmarksVersion(), false);
  158.  
  159.             if(!this._ignoreAuthTokens){
  160.                 var auth = Xmarks.gSettings.auth || "";
  161.                 if(auth.length > 0){
  162.                     this._channel.setRequestHeader("Authorization", 
  163.                         "XMAuth " + auth, false);
  164.                     this._channel.setRequestHeader("X-Xmarks-Auth",
  165.                         auth, false);
  166.                 }
  167.             }
  168.  
  169.             // Set other headers if provided.
  170.             if (this._headers) {
  171.                 var self = this;
  172.                 forEach(this._headers, function(v, k) {
  173.                     self._channel.setRequestHeader(k, v, false);
  174.                 } );
  175.             }
  176.         }
  177.  
  178.         // Create a stream loader for retrieving the response.
  179.         this._streamLoader = Cc["@mozilla.org/network/stream-loader;1"]
  180.             .createInstance(Ci.nsIStreamLoader);
  181.  
  182.         // Fire it up. Note that this results in the channel's AsyncOpen
  183.         // being called; if we have set an upload stream above, it will be
  184.         // transmitted as part of the request. This is a bit strange, but
  185.         // appears to be correct.
  186.         try {
  187.             // Before Firefox 3...
  188.             this._streamLoader.init(this._channel, this, null);
  189.         } catch(e) {
  190.             // Firefox 3 style...
  191.             this._streamLoader.init(this);
  192.             this._channel.asyncOpen(this._streamLoader, null);
  193.         }
  194.         return;
  195.     },
  196.  
  197.     Cancel: function() {
  198.         if (this._channel && this._channel.isPending()){
  199.             Xmarks.LogWrite("Cancelling Network Request");
  200.             this._channel.cancel(0x804b0002);
  201.         }
  202.     },
  203.  
  204.     onStreamComplete: function(loader, ctxt, status, resultLength, result) {
  205.         var response = {};
  206.         if (Components.isSuccessCode(status)) {
  207.             try {
  208.                 status = this._channel.responseStatus || 200;
  209.             } catch (e) {
  210.                 this._callback( { status: 1005, errormsg: 
  211.                         "Disable automatic proxy settings detection."} );
  212.                 return;
  213.             }
  214.  
  215.             //try {
  216.             //    dump("cookie: " + this._channel.getResponseHeader("Set-Cookie") + "\n");
  217.            // }catch(e){}
  218.             var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  219.                 createInstance(Ci.nsIScriptableUnicodeConverter);
  220.             converter.charset = "utf-8";
  221.             var msg = "";
  222.             if (status == 200 || status == 201 || status == 204) {
  223.                 if(!this._ignoreBody){
  224.                     msg = converter.convertFromByteArray(result, resultLength);
  225.                 }
  226.                 if (msg && msg.length && !this._ignoreBody) {
  227.                     if(this._noJSON){
  228.                         response = {
  229.                             status: 0,
  230.                             txt: msg
  231.                         };
  232.                     } else {
  233.                         try {
  234.                             response = this.JSON ? this.JSON.decode(msg) :
  235.                                 eval("(" + msg + ")");
  236.                             if(response.auth && response.auth.length > 0){
  237.                                 Xmarks.gSettings.auth = response.auth;
  238.                             }
  239.  
  240.                             if(response.username && response.username.toLowerCase() != Xmarks.gSettings.username.toLowerCase()  && Xmarks.gSettings.username.indexOf('@') < 0 ){
  241.                                 response = { status: 1010, 
  242.                                     errormsg: "Invalid server response" }; 
  243.                                 Xmarks.LogWrite("CurrUser: " + Xmarks.gSettings.username);
  244.                                 Xmarks.LogWrite("ServerUser: " + response.username);
  245.                                 Xmarks.LogWrite("JSON Response: " + msg);
  246.                             }
  247.                         } catch(e) {
  248.                             Xmarks.LogWrite("Failed to parse message: " + msg);
  249.                             response = { status: 1010, 
  250.                                 errormsg: "Invalid server response" }; 
  251.                         }
  252.                     }
  253.                 }
  254.                 if (response.status == null) {
  255.                     response.status = 0;
  256.                 }
  257.  
  258.                 // New Auth stuff
  259.                 if(response.status == 302){
  260.                     function RestartAfterAuth(response) {
  261.                         if (response.status == 0){
  262.                             Xmarks.gSettings.auth = response.auth;
  263.                             // We're authenticated. Retry original request.
  264.                             self.Start(self._callback);
  265.                             return;
  266.                         } else {
  267.                             // Auth failed. Return failure code.
  268.                             if (response.message == "Wrong username or password.") {
  269.                                 response.status = 401;
  270.                             }
  271.                             self._callback(response);
  272.                             return;
  273.                         }
  274.                     }
  275.  
  276.                     Xmarks.LogWrite(">>> Authenticating...");
  277.  
  278.                     if (this._triedAuth) {
  279.                         Xmarks.LogWrite("In authentication loop. Possible proxy server caching issue.");
  280.                         this._callback( { status: 1012 } );
  281.                         return;
  282.                     }
  283.  
  284.                     if (this._noAuthDialog) {
  285.                         if ((Xmarks.gSettings.rememberPassword && 
  286.                                 Xmarks.gSettings.masterPasswordSet) ||
  287.                             (!Xmarks.gSettings.rememberPassword && 
  288.                                 !Xmarks.gSettings.sessionPassword)) {
  289.                             Xmarks.LogWrite("Client skips authentication");
  290.                             this._callback( { status: 403 } );
  291.                             return;
  292.                         }
  293.                     }
  294.  
  295.                     var location = response.authtoken_location;
  296.                     var self = this;    // keep a handle on the original request
  297.                     // parse out the protocol, host, and path
  298.                     var colonslashslash = location.indexOf("://");
  299.                     var nextslash = location.indexOf("/", colonslashslash + 3);
  300.                     if (colonslashslash < 0 || nextslash < 0) {
  301.                         throw Error("Couldn't parse location string " + location);
  302.                     }
  303.                     var url = {}
  304.                     url.host = location.substr(colonslashslash + 3, 
  305.                         nextslash - (colonslashslash + 3)); 
  306.                     url.host = Xmarks.gSettings.getCharPref('host-login', url.host);
  307.  
  308.                     url.path = location.substr(nextslash);
  309.                     Xmarks.LogWrite("Requesting password.");
  310.                     try {
  311.                         var pw = Xmarks.gSettings.password;
  312.                     } catch (e) {
  313.                         Xmarks.LogWrite("User canceled password request.");
  314.                         this._callback(2);
  315.                         return;
  316.                     }
  317.                     var authReq = new Request(
  318.                         "POST", 
  319.                         url,
  320.                         { 
  321.                             username: Xmarks.gSettings.username, 
  322.                             password: Base64.encode(pw)
  323.                         },
  324.                         { 
  325.                             isAuthRequest: true
  326.                         }
  327.                     );
  328.                     this._triedAuth = true;
  329.                     authReq.Start(RestartAfterAuth);
  330.                     return;
  331.                 }
  332.                 
  333.             } else {
  334.                 response = { status: status, "errormsg" : msg };
  335.             }
  336.             try {   // Pass back the etag if there is one.
  337.                 response.etag = this._channel.getResponseHeader("Etag");
  338.             } catch (e) {}
  339.  
  340.             if(Xmarks.gSettings.getDebugOption("no-verbose")){
  341.                 Xmarks.LogWrite(">>> Callback (disabled)");
  342.             } else {
  343.                 var oldauth = response.auth;
  344.                 delete response.auth;
  345.                 if(this._noJSON){
  346.                     Xmarks.LogWrite(">>> Callback (not json, status: " + response.status + ")");
  347.                 } else {
  348.                     Xmarks.LogWrite(">>> Callback " + response.toSource());
  349.                 }
  350.                 response.auth = oldauth;
  351.             }
  352.             this._callback(response);
  353.             return;
  354.         } else {
  355.             if (status == NS_ERROR_REDIRECT_LOOP && !this._isAuthRequest) {
  356.                 this._callback( { status: status });
  357.                 return;
  358.             } else if (status == NS_ERROR_CWD_ERROR) {
  359.                 Xmarks.LogWrite("CWD error (mapping to 404)");
  360.                 this._callback( { status: 404 } );
  361.             } else {
  362.                 Xmarks.LogWrite("network request failed; status is " +
  363.                         status.toString(16));
  364.                 this._callback( { status: status });
  365.             }
  366.         }
  367.     },
  368.  
  369.     _StripPassword: function(url) {
  370.         var exp = /^(.*):(.*)@(.*)$/
  371.         var match = url.match(exp);
  372.         return match ? match[1] + "@" + match[3] : url;
  373.     }
  374. }
  375.  
  376.